#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <string>
#include <type_traits>
#include "distance.hpp"

#undef NDEBUG

#include <cassert>
#include <algorithm>

namespace GraphGradient {

    std::vector<std::vector<double>>
    get_graph_distance(std::vector<std::vector<int>> &graph, float *base, int vec_dim) {
        std::vector<std::vector<double>> res;
        int n_vertex = graph.size();
        res.resize(n_vertex);
        for (int i = 0; i < n_vertex; i++) {
            std::vector<int> neighbor_l = graph[i];
            int neighbor_size = neighbor_l.size();
            res[i].resize(neighbor_size);
            for (int j = 0; j < neighbor_size; j++) {
                double dist = l2distance(base + i * vec_dim, base + neighbor_l[j] * vec_dim, vec_dim);
                res[i][j] = dist;
            }
        }
        return res;
    }

    void save_graph_text(std::vector<std::vector<int>> &graph, float *base, int vec_dim, const char *graph_method,
                         const char *base_path, int enter_point) {
        int n_vertex = graph.size();
        char path[256];
        std::ofstream out_graph;
        std::sprintf(path, "%s/%s-text.graph", base_path, graph_method);
        out_graph.open(path);
        out_graph << n_vertex << " " << enter_point << std::endl;
        for (int i = 0; i < n_vertex; i++) {
            std::vector<int> tmp_vector = graph[i];
            int tmp_vector_size = tmp_vector.size();
            out_graph << tmp_vector_size << " ";
            for (std::vector<int>::iterator iter = tmp_vector.begin(); iter != tmp_vector.end(); iter++) {
                int neighbor = *iter;
                out_graph << neighbor << " ";
            }
            out_graph << std::endl;
        }
        out_graph.close();

        std::vector<std::vector<double>> graph_distance = get_graph_distance(graph, base, vec_dim);
        std::ofstream out_dist_graph;
        std::sprintf(path, "%s/%s-distance-text.graph", base_path, graph_method);
        out_dist_graph.open(path);
        out_dist_graph << n_vertex << " " << enter_point << std::endl;
        for (int i = 0; i < n_vertex; i++) {
            std::vector<double> tmp_vector = graph_distance[i];
            int tmp_vector_size = tmp_vector.size();
            out_dist_graph << tmp_vector_size << " ";
            for (std::vector<double>::iterator iter = tmp_vector.begin(); iter != tmp_vector.end(); iter++) {
                double neighbor_dist = *iter;
                out_dist_graph << neighbor_dist << " ";
            }
            out_dist_graph << std::endl;
        }
        out_dist_graph.close();
        std::printf("save graph success\n");
    }

    template<typename T>
    void read_graph_text(const char *path, std::vector<std::vector<T>> &out_graph, int &out_enter_point) {
        std::ifstream instream;
        instream.open(path, std::ios::in);
        if (!instream.is_open()) {//判断文件是否打开
            std::cout << "Error opening file" << std::endl;
        }
        int n_vertex;
        instream >> n_vertex >> out_enter_point;
        out_graph.clear();
        out_graph.resize(n_vertex);
        std::string line;
        getline(instream, line);
        for (int i = 0; i < n_vertex; i++) {
            getline(instream, line);
            int line_size = line.size();
            char *input = new char[line_size + 1];
            strcpy(input, line.c_str());
            char *token = std::strtok(input, " ");
            int size = std::atoi(token);
            out_graph[i].resize(size);
            for (int j = 0; j < size; j++) {
                token = std::strtok(NULL, " ");
                if (std::is_same<int, T>::value) {
                    int tmp_num = std::atoi(token);
                    out_graph[i][j] = tmp_num;
                } else if (std::is_same<double, T>::value) {
                    double tmp_num = std::atof(token);
                    out_graph[i][j] = tmp_num;
                } else {
                    std::cout << "not support data type" << std::endl;
                    return;
                }
            }
            delete[] input;
        }
        std::printf("read vector graph success\n");
    }

    void save_graph(std::vector<std::vector<int>> &graph, float *base, int vec_dim, const char *graph_method,
                    const char *base_path, int enter_point) {
        char path[256];
        std::sprintf(path, "%s/%s.graph", base_path, graph_method);
        std::ofstream out(path, std::ios::binary | std::ios::out);
        int n_vertex = graph.size();
        out.write((char *) &n_vertex, sizeof(int));
        out.write((char *) &enter_point, sizeof(int));
        for (int i = 0; i < n_vertex; i++) {
            int graph_i_size = graph[i].size();
            out.write((char *) &graph_i_size, sizeof(int));
            out.write((char *) graph[i].data(), graph_i_size * sizeof(int));
        }
        out.close();

        std::sprintf(path, "%s/%s-distance.graph", base_path, graph_method);
        std::ofstream out_dist(path, std::ios::binary | std::ios::out);
        std::vector<std::vector<double>> graph_distance = get_graph_distance(graph, base, vec_dim);
        out_dist.write((char *) &n_vertex, sizeof(int));
        out_dist.write((char *) &enter_point, sizeof(int));
        for (int i = 0; i < n_vertex; i++) {
            int graph_i_size = graph_distance[i].size();
            out_dist.write((char *) &graph_i_size, sizeof(int));
            out_dist.write((char *) graph_distance[i].data(), graph_i_size * sizeof(double));
        }
        out_dist.close();
        std::cout << "save graph success" << std::endl;
    }

    template<typename T>
    void read_graph(const char *path, std::vector<std::vector<T>> &out_graph, int &out_enter_point) {
        std::ifstream in(path, std::ios::binary);
        if (!in.is_open()) {
            std::cerr << "Open file error" << std::endl;
            exit(-1);
        }
        int n_vertex = 0;
        in.read((char *) &n_vertex, sizeof(int));
        in.read((char *) &out_enter_point, sizeof(int));
        out_graph.clear();
        out_graph.resize(n_vertex);

        for (int i = 0; i < n_vertex; i++) {
            int k;
            in.read((char *) &k, sizeof(int));
            if (in.eof()) {
                return;
            }
            std::vector<T> tmp(k);
            in.read((char *) tmp.data(), k * sizeof(T));
            out_graph[i] = tmp;
        }
    }

}